#include <thread>
#include <gtest/gtest.h>
#include <libgo/libgo.h>

#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>
////////////////////////////////////////////////////////////////////////////

class COEnvironment
    : public testing::Environment {
public:
    COEnvironment();
    virtual ~COEnvironment();
public:
    virtual void SetUp();
    virtual void TearDown();
};

COEnvironment::COEnvironment() {
}

COEnvironment::~COEnvironment() {
}

void COEnvironment::TearDown() {
    testing::Environment::TearDown();
}

void COEnvironment::SetUp() {
    testing::Environment::SetUp();
}

#ifndef LIBGO_SYS_Windows
int tcpSocketPair(int, int, int, int fds[2]) {
    int listenSock = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSock < 0) {
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(0);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = bind(listenSock, (sockaddr*)&addr, sizeof(addr));
    if (res < 0) {
        close(listenSock);
        return -1;
    }
    listen(listenSock, 5);

    socklen_t addrLen = sizeof(addr);
    res = getsockname(listenSock, (sockaddr*)&addr, &addrLen);
    if (res < 0) {
        close(listenSock);
        return -1;
    }

    int clientSock = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSock < 0) {
        close(listenSock);
        return -1;
    }

    std::thread([=] {
        connect(clientSock, (sockaddr*)&addr, addrLen);
    }).detach();

    struct pollfd pfd = {};
    pfd.fd = listenSock;
    pfd.events = POLLIN;
    res = poll(&pfd, 1, 1000);
    if (res <= 0) {
        close(listenSock);
        close(clientSock);
        return -1;
    }

    int newSock = accept(listenSock, (sockaddr*)&addr, &addrLen);
    if (newSock <= 0) {
        close(listenSock);
        close(clientSock);
        return -1;
    }

    int bufSize = 32 * 1024;
    setsockopt(newSock, SOL_SOCKET, SO_RCVBUF, (const char*)&bufSize, sizeof(int));
    setsockopt(newSock, SOL_SOCKET, SO_SNDBUF, (const char*)&bufSize, sizeof(int));
    setsockopt(clientSock, SOL_SOCKET, SO_RCVBUF, (const char*)&bufSize, sizeof(int));
    setsockopt(clientSock, SOL_SOCKET, SO_SNDBUF, (const char*)&bufSize, sizeof(int));

    fds[0] = clientSock;
    fds[1] = newSock;
    //close(listenSock);
    return 0;
}
#endif

////////////////////////////////////////////////////////////////////////////

#define TEST_MIN_THREAD 4
#define TEST_MAX_THREAD 4

////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {

#if defined(LIBGO_SYS_Windows)
    struct __autoInitWSA {
        __autoInitWSA() {
            WSAData wsaData;
            WSAStartup(MAKEWORD(2, 2), &wsaData);
        }
    };
    __autoInitWSA g___autoInitWSA;
#endif

    testing::AddGlobalTestEnvironment(new COEnvironment);
    testing::InitGoogleTest(&argc, argv);

    std::thread t([] { co_sched.Start(TEST_MIN_THREAD, TEST_MAX_THREAD); });
    t.detach();

    return RUN_ALL_TESTS();
}
